// 版权所有2016 etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package embed

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"go.etcd.io/etcd/client/pkg/v3/logutil"
	"go.etcd.io/etcd/client/pkg/v3/srv"
	"go.etcd.io/etcd/client/pkg/v3/tlsutil"
	"go.etcd.io/etcd/client/pkg/v3/transport"
	"go.etcd.io/etcd/client/pkg/v3/types"
	"go.etcd.io/etcd/pkg/v3/flags"
	"go.etcd.io/etcd/pkg/v3/netutil"
	"go.etcd.io/etcd/server/v3/config"
	"go.etcd.io/etcd/server/v3/etcdserver"
	"go.etcd.io/etcd/server/v3/etcdserver/api/membership"
	"go.etcd.io/etcd/server/v3/etcdserver/api/v3compactor"

	bolt "go.etcd.io/bbolt"
	"go.uber.org/multierr"
	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"
	"google.golang.org/grpc"
	"sigs.k8s.io/yaml"
)

const (
	ClusterStateFlagNew      = "new"
	ClusterStateFlagExisting = "existing"

	DefaultName                        = "default"
	DefaultMaxSnapshots                = 5
	DefaultMaxWALs                     = 5
	DefaultMaxTxnOps                   = uint(128)
	DefaultWarningApplyDuration        = 100 * time.Millisecond
	DefaultWarningUnaryRequestDuration = 300 * time.Millisecond
	DefaultMaxRequestBytes             = 1.5 * 1024 * 1024
	DefaultGRPCKeepAliveMinTime        = 5 * time.Second
	DefaultGRPCKeepAliveInterval       = 2 * time.Hour
	DefaultGRPCKeepAliveTimeout        = 20 * time.Second
	DefaultDowngradeCheckTime          = 5 * time.Second

	DefaultListenPeerURLs   = "http:// localhost:2380“
	DefaultListenClientURLs = "http:// localhost:2379”

	DefaultLogOutput = "default"
	JournalLogOutput = "systemd/journal"
	StdErrLogOutput  = "stderr"
	StdOutLogOutput  = "stdout"

	// DefaultLogRotationConfig是用于日志旋转的默认配置。
	// 默认情况下禁用日志旋转。
	// MaxSize=100 
	// MaxAge=0 
	// MaxBackup=0 
	// LocalTime=false 
	// Compress=false 
	DefaultLogRotationConfig = `{"maxsize": 100, "maxage": 0, "maxbackups": 0, "localtime": false, "compress": false}`

	// Experimental Distributed TracingAddress是默认的收集器地址。
	ExperimentalDistributedTracingAddress = "localhost:4317"
	// ExperimentalDistributedTracingServiceName是默认的etcd服务名称。
	ExperimentalDistributedTracingServiceName = "etcd"

	// DefaultStrictReconfigCheck是“-strict reconfig check”标志的默认值。
	// 默认启用。
	DefaultStrictReconfigCheck = true
	// DefaultEnableV2是“-enable-v2”标志的默认值。
	// v2 API默认为禁用。
	DefaultEnableV2 = false

	// maxElectionMs指定选择超时的最大值。
	// 更多详细信息列在/文档/调优。md#时间参数。
	maxElectionMs = 50000
	// 后端自由列表映射类型
	freelistArrayType = "array"
)

var (
	ErrConflictBootstrapFlags = fmt.Errorf("multiple discovery or bootstrap flags are set. " +
		"Choose one of \"initial-cluster\", \"discovery\" or \"discovery-srv\"")
	ErrUnsetAdvertiseClientURLsFlag = fmt.Errorf("--advertise-client-urls is required when --listen-client-urls is set explicitly")
	ErrLogRotationInvalidLogOutput  = fmt.Errorf("--log-outputs requires a single file path when --log-rotate-config-json is defined")

	DefaultInitialAdvertisePeerURLs = "http:// 本地主机：2380“
	DefaultAdvertiseClientURLs      = "http:// 本地主机：2379”

	defaultHostname   string
	defaultHostStatus error

	// 用于测试
	getCluster = srv.GetCluster
)

var (
	// 压缩模式周期性压缩模式
	// 用于“Config.AutoCompactionMode”字段。
	// 如果“自动压缩模式”为CompactorModePeriodic，而
	// “自动压缩保留”为“1h”，则它会自动压缩
	// 每小时压缩一次存储。
	CompactorModePeriodic = v3compactor.ModePeriodic

	// CompactorModerVersion是基于版本的压缩模式
	// 用于“Config.AutoCompactionMode”字段。
	// 如果“自动压缩模式”为CompactorModerVersion，而
	// “自动压缩保留”为“1000”，则在当前版本为6000时压缩登录
	// 版本5000。
	// 如果有足够的日志进行，则每5分钟运行一次。
	CompactorModeRevision = v3compactor.ModeRevision
)

func init() {
	defaultHostname, defaultHostStatus = netutil.GetDefaultHost()
}

// Config保存配置etcd服务器的参数。
type Config struct {
	Name   string `json:"name"`
	Dir    string `json:"data-dir"`
	WalDir string `json:"wal-dir"`

	SnapshotCount uint64 `json:"snapshot-count"`

	// SnapshotCatchUpEntries是慢速跟随者压缩raft存储条目后追赶的条目数
	// 。
	// 我们希望跟随者与引导者之间有毫秒级的延迟。
	// 最大吞吐量约为10K。保持5公里的参赛成绩足以帮助
	// 追随者迎头赶上。
	// 警告：仅对测试更改此选项。
	// 始终使用“DefaultSnapshotCatchUpEntries”
	SnapshotCatchUpEntries uint64

	MaxSnapFiles uint `json:"max-snapshots"`
	MaxWalFiles  uint `json:"max-wals"`

	// TickMs是心跳节拍之间的毫秒数。
	// TODO:将滴答声和心跳滴答声解耦（当前心跳滴答声=1）。
	// 使勾号成为集群范围的配置。
	TickMs     uint `json:"heartbeat-interval"`
	ElectionMs uint `json:"election-timeout"`

	// 初始选举滴答前进为真，则本地成员快进
	// 选举滴答声加快“初始”领导人选举触发。本
	// 有利于更大的选举周期。例如，跨
	// 数据中心部署可能需要更长的10秒选择超时。
	// 如果为真，则本地节点不需要等待10秒。取而代之的是，
	// 在领导人选举前将其选举时间缩短到8秒，只剩下2秒。
	// 
	// 主要假设是：
	// -集群没有活跃的领导者，因此前进的滴答声可以加快
	// 领导者选举，或者
	// -集群已经有了一个固定的领导者，而重新加入追随者
	// 很可能会在提前点击
	// 后和选举暂停前收到领导者的心跳。
	// 
	// 但是，当从领导者到重新加入追随者的网络拥挤时，
	// 并且追随者在左选举中没有接收到领导者心跳
	// 滴答声，必须发生破坏性选举，从而影响集群
	// 可用性。
	// 
	// 禁用此选项将减慢跨
	// 数据中心部署的初始引导过程。通过配置
	// /--初始选举提前，以缓慢的初始引导为代价，做出您自己的权衡。
	// 
	// 如果是单节点，则不管如何都会前进。
	// 
	// 请参阅https:
	InitialElectionTickAdvance bool `json:"initial-election-tick-advance"`

	// BackendBatchInterval是提交后端事务之前的最长时间。
	BackendBatchInterval time.Duration `json:"backend-batch-interval"`
	// BackendBatchLimit是提交后端事务之前的最大操作数。
	BackendBatchLimit int `json:"backend-batch-limit"`
	// BackendFreelistType指定boltdb后端使用的自由列表类型（支持数组和映射类型）。
	BackendFreelistType string `json:"backend-bbolt-freelist-type"`
	QuotaBackendBytes   int64  `json:"quota-backend-bytes"`
	MaxTxnOps           uint   `json:"max-txn-ops"`
	MaxRequestBytes     uint   `json:"max-request-bytes"`

	LPUrls, LCUrls []url.URL
	APUrls, ACUrls []url.URL
	ClientTLSInfo  transport.TLSInfo
	ClientAutoTLS  bool
	PeerTLSInfo    transport.TLSInfo
	PeerAutoTLS    bool
	// SelfSignedCertValidity指定etcd在指定ClientAutoTLS和PeerautTLS时自动生成的客户端和对等证书
	// 的有效期，
	// 单位为年，默认值为1 
	SelfSignedCertValidity uint `json:"self-signed-cert-validity"`

	// CipherSuite是
	// 客户端/服务器和对等方之间支持的TLS密码套件的列表。如果为空，Go自动填充列表。
	// 请注意，密码套件按给定顺序排列优先级。
	CipherSuites []string `json:"cipher-suites"`

	ClusterState          string `json:"initial-cluster-state"`
	DNSCluster            string `json:"discovery-srv"`
	DNSClusterServiceName string `json:"discovery-srv-name"`
	Dproxy                string `json:"discovery-proxy"`
	Durl                  string `json:"discovery"`
	InitialCluster        string `json:"initial-cluster"`
	InitialClusterToken   string `json:"initial-cluster-token"`
	StrictReconfigCheck   bool   `json:"strict-reconfig-check"`

	// EnableV2公开不推荐使用的V2 API表面。
	// TODO:在3.6中删除（https:
	// 在3.5中不推荐使用。
	EnableV2 bool `json:"enable-v2"`

	// 自动压缩模式为“定期”或“修订”。
	AutoCompactionMode string `json:"auto-compaction-mode"`
	// 自动压缩保留为带有时间单位
	// （例如5分钟为“5m”）的持续时间字符串或修订单位（例如“5000”）.
	// 如果未提供时间单位且压缩模式为“周期”，
	// 单位默认为小时。例如，“5”转换为5小时。
	AutoCompactionRetention string `json:"auto-compaction-retention"`

	// GRPCKeepAliveMinTime是客户端在ping服务器之前应等待的最小间隔
	// 当客户端ping“太快”时，服务器
	// 发送goaway并关闭连接（错误：ping太多，
	// http2.ErrCodeEnhanceYourCalm）。如果太慢，则什么也不会发生。
	// 服务器仅在存在任何活动流
	// （PermitWithoutStream设置为false）时才需要客户端ping.
	GRPCKeepAliveMinTime time.Duration `json:"grpc-keepalive-min-time"`
	// GRPCKeepAliveInterval是服务器到客户端ping 
	// 以检查连接是否处于活动状态的频率。在额外的超时时间后关闭非响应连接
	// 0禁用。
	GRPCKeepAliveInterval time.Duration `json:"grpc-keepalive-interval"`
	// GRPCKeepAliveTimeout是额外的等待时间
	// 关闭非响应连接之前。0禁用。
	GRPCKeepAliveTimeout time.Duration `json:"grpc-keepalive-timeout"`

	// SocketOpts是传递给侦听器配置的套接字选项。
	SocketOpts transport.SocketOpts `json:"socket-options"`

	// PreVote为true以启用Raft预投票。
	// 如果启用，Raft将运行额外的选举阶段
	// 以检查它是否能够获得足够的投票以支持w在
	// 中进行一次选择，从而最大限度地减少中断。
	PreVote bool `json:"pre-vote"`

	CORS map[string]struct{}

	// 主机白名单列出HTTP客户端请求中可接受的主机名。
	// 客户端源策略可防止“DNS重新绑定”攻击
	// 到不安全的etcd服务器。也就是说，任何网站只需创建
	// 一个授权的DNS名称，并将DNS定向到“localhost”（或任何
	// 其他地址）。然后，etcd服务器的所有HTTP端点在“localhost”上侦听
	// 即可变得可访问，因此容易受到DNS重新绑定
	// 攻击。有关更多详细信息，请参阅“CVE-2018-5702”。
	// 
	// 1.如果客户端连接通过HTTPS是安全的，则允许使用任何主机名。
	// 2.如果客户端连接不安全，则使用“主机白名单”不为空，
	// 仅允许主机字段列在白名单中的HTTP请求。
	// 
	// 请注意，无论是否启用身份验证
	// 都将强制执行客户端源策略，以实现更严格的控制。默认情况下，
	// 
	// ，“主机白名单”是“*”，它允许任何主机名。请注意，在指定主机名时，不会自动添加环回地址。要允许环回接口，请将其保留为空或设置为“*”、
	// 或手动将其添加到白名单中（例如“localhost”、“127.0.0.1”等）.
	// 
	// CVE-2018-5702参考：
	// -https:
	// -https:
	// -https:
	HostWhitelist map[string]struct{}

	// UserHandlers用于注册用户处理程序，仅用于
	// 将etcd嵌入其他应用程序。
	// 映射键为处理程序的路由路径，以及
	// 必须确保它不会与etcd的冲突。
	UserHandlers map[string]http.Handler `json:"-"`
	// ServiceRegister用于注册用户的gRPC服务。一个简单的用法示例：
	// cfg:=embed。NewConfig（）
	// cfg。ServerRegister=func（s*grpc.Server）{
	// pb.RegisterFooServer（s，&fooServer{}）
	// pb.registerbaseserver（s，&barServer{}）
	// }
	// embed。StartEtcd（cfg）
	ServiceRegister func(*grpc.Server) `json:"-"`

	AuthToken  string `json:"auth-token"`
	BcryptCost uint   `json:"bcrypt-cost"`

	// 简单令牌的AuthTokenTTL（以秒为单位）
	AuthTokenTTL uint `json:"auth-token-ttl"`

	ExperimentalInitialCorruptCheck bool          `json:"experimental-initial-corrupt-check"`
	ExperimentalCorruptCheckTime    time.Duration `json:"experimental-corrupt-check-time"`
	// ExperimentalEnableV2V3配置公开在V3存储上工作的不推荐的V2 API的URL。
	// 在v3中已弃用。5.
	// TODO:在v3中删除。6.（https:
	ExperimentalEnableV2V3 string `json:"experimental-enable-v2v3"`
	// experimentableleaseckpoint允许leader向其他成员发送定期检查点，以防止在leader更改时重置剩余TTL。
	ExperimentalEnableLeaseCheckpoint bool `json:"experimental-enable-lease-checkpoint"`
	// experimentableleaseckpointpersist启用持久化剩余TTL以防止长期租约的无限期自动续订。始终在v3中启用。6.应用于确保从启用此功能的v3.5群集顺利升级。
	// 要求启用实验性启用租约检查点。
	// 在v3.6中已弃用。
	// TODO:在v3.7中删除
	ExperimentalEnableLeaseCheckpointPersist bool `json:"experimental-enable-lease-checkpoint-persist"`
	ExperimentalCompactionBatchLimit         int  `json:"experimental-compaction-batch-limit"`
	// 实验性CompactionSleepInterval是两者之间的睡眠间隔y etcd压缩循环。
	ExperimentalCompactionSleepInterval     time.Duration `json:"experimental-compaction-sleep-interval"`
	ExperimentalWatchProgressNotifyInterval time.Duration `json:"experimental-watch-progress-notify-interval"`
	// ExperimentalWarningApplyDuration是当应用请求
	// 花费的时间超过此值时生成警告的持续时间。
	ExperimentalWarningApplyDuration time.Duration `json:"experimental-warning-apply-duration"`
	// ExperimentalBootstrapDefragThresholdMegabytes是etcd服务器t需要释放的最小MB数O AbDefg
	ExperimentalBootstrapDefragThresholdMegabytes uint `json:"experimental-bootstrap-defrag-threshold-megabytes"`
	ExperimentalWarningUnaryRequestDuration time.Duration `json:"experimental-warning-unary-request-duration"`
	ExperimentalMaxLearners int `json:"experimental-max-learners"`

	// ForceNewCluster即使以前启动过也会启动新群集；不安全。
	ForceNewCluster bool `json:"force-new-cluster"`

	EnablePprof           bool   `json:"enable-pprof"`
	Metrics               string `json:"metrics"`
	ListenMetricsUrls     []url.URL
	ListenMetricsUrlsJSON string `json:"listen-metrics-urls"`

	// ExperimentalEnabledDistributedTracing表示是否启用了使用OpenTelemetry的实验跟踪。
	ExperimentalEnableDistributedTracing bool `json:"experimental-enable-distributed-tracing"`
	// ExperimentalDistributedTracingAddress是OpenTelemetry Collector的地址。只有当ExperimentalDistributedTracing为true时，才能设置
	// 
	ExperimentalDistributedTracingAddress string `json:"experimental-distributed-tracing-address"`
	// ExperimentalDistributedTracingServiceName是服务的名称。
	// 只能在ExperimentalDistributedTracing为true时使用。AbEFG
	ExperimentalDistributedTracingServiceName string `json:"experimental-distributed-tracing-service-name"`
	// 此ID必须是唯一的，这有助于区分同一服务的实例
	// 同时存在。
	// 只能在ExperimentableDistributedTracing为true时使用。
	ExperimentalDistributedTracingServiceInstanceID string `json:"experimental-distributed-tracing-instance-id"`
	// ExperimentalDistributedTracingSamplingRatePerMillion是每百万个跨度要采集的样本数。
	// 默认值为0。
	ExperimentalDistributedTracingSamplingRatePerMillion int `json:"experimental-distributed-tracing-sampling-rate"`

	// 记录器是记录器选项：当前仅支持“zap”。
	// “capnslog”已在v3.5中删除。
	Logger string `json:"logger"`
	// 日志级别配置日志级别。仅支持调试、信息、警告、错误、死机或致命。默认值为“信息”。
	LogLevel string `json:"log-level"`
	// 日志格式设置日志编码。仅支持json，控制台。默认值为“json”。
	LogFormat string `json:"log-format"`
	// 登录输出为：
	// /-“默认值”作为os.Stderr，
	// /-“Stderr”作为os.Stderr，
	// /-“stdout”作为os.stdout，
	// /-“Logger”时可以是多个附加服务器日志的文件路径is zap.
	LogOutputs []string `json:"log-outputs"`
	// EnableLogRotation启用单个Logoutput文件目标的日志旋转。
	EnableLogRotation bool `json:"enable-log-rotation"`
	// LogRotationConfigJSON是一个直通，允许直接传递日志旋转JSON配置。
	LogRotationConfigJSON string `json:"log-rotation-config-json"`
	// ZapLogger Builder用于构建zap logger。
	ZapLoggerBuilder func(*Config) error

	// logger日志服务器端opera默认值为nil、
	// 和“setupLogging”必须在启动服务器之前调用。
	// 不要直接设置记录器。
	loggerMu *sync.RWMutex
	logger   *zap.Logger
	// EnableGRPCGateway启用grpc网关。
	// 网关将RESTful HTTP API转换为grpc。
	EnableGRPCGateway bool `json:"enable-grpc-gateway"`

	// UnsafeNoFsync禁用所有fsync的使用。
	// 设置这是不安全的，将导致e数据丢失。
	UnsafeNoFsync bool `json:"unsafe-no-fsync"`

	ExperimentalDowngradeCheckTime time.Duration `json:"experimental-downgrade-check-time"`

	// ExperimentalMemoryMlock启用etcd拥有的内存页的MLock。
	// 该设置改善了环境中的etcd尾部延迟：
	// -内存压力可能导致将页交换到磁盘
	// -磁盘延迟可能不稳定
	// 当前所有etcd内存都被mlock，但将来该标志可以
	// 被细化为仅在bbolt的使用区域中被mlock。
	ExperimentalMemoryMlock bool `json:"experimental-memory-mlock"`

	// ExperimentalTxnModeWriteWithSharedBuffer使写事务能够在其只读检查操作中使用共享缓冲区。
	ExperimentalTxnModeWriteWithSharedBuffer bool `json:"experimental-txn-mode-write-with-shared-buffer"`

	// V2Deprecation描述了API和存储V2支持的阶段
	V2Deprecation config.V2DeprecationEnum `json:"v2-deprecation"`
}

// configYAML保存适合于yaml解析的配置
type configYAML struct {
	Config
	configJSON
}

// configJSON具有转换为配置选项的文件选项
type configJSON struct {
	LPUrlsJSON string `json:"listen-peer-urls"`
	LCUrlsJSON string `json:"listen-client-urls"`
	APUrlsJSON string `json:"initial-advertise-peer-urls"`
	ACUrlsJSON string `json:"advertise-client-urls"`

	CORSJSON          string `json:"cors"`
	HostWhitelistJSON string `json:"host-whitelist"`

	ClientSecurityJSON securityConfig `json:"client-transport-security"`
	PeerSecurityJSON   securityConfig `json:"peer-transport-security"`
}

type securityConfig struct {
	CertFile       string `json:"cert-file"`
	KeyFile        string `json:"key-file"`
	ClientCertFile string `json:"client-cert-file"`
	ClientKeyFile  string `json:"client-key-file"`
	CertAuth       bool   `json:"client-cert-auth"`
	TrustedCAFile  string `json:"trusted-ca-file"`
	AutoTLS        bool   `json:"auto-tls"`
}

// NewConfig创建一个使用默认值填充的新配置。
func NewConfig() *Config {
	lpurl, _ := url.Parse(DefaultListenPeerURLs)
	apurl, _ := url.Parse(DefaultInitialAdvertisePeerURLs)
	lcurl, _ := url.Parse(DefaultListenClientURLs)
	acurl, _ := url.Parse(DefaultAdvertiseClientURLs)
	cfg := &Config{
		MaxSnapFiles: DefaultMaxSnapshots,
		MaxWalFiles:  DefaultMaxWALs,

		Name: DefaultName,

		SnapshotCount:          etcdserver.DefaultSnapshotCount,
		SnapshotCatchUpEntries: etcdserver.DefaultSnapshotCatchUpEntries,

		MaxTxnOps:                        DefaultMaxTxnOps,
		MaxRequestBytes:                  DefaultMaxRequestBytes,
		ExperimentalWarningApplyDuration: DefaultWarningApplyDuration,

		ExperimentalWarningUnaryRequestDuration: DefaultWarningUnaryRequestDuration,

		GRPCKeepAliveMinTime:  DefaultGRPCKeepAliveMinTime,
		GRPCKeepAliveInterval: DefaultGRPCKeepAliveInterval,
		GRPCKeepAliveTimeout:  DefaultGRPCKeepAliveTimeout,

		SocketOpts: transport.SocketOpts{
			ReusePort:    false,
			ReuseAddress: false,
		},

		TickMs:                     100,
		ElectionMs:                 1000,
		InitialElectionTickAdvance: true,

		LPUrls: []url.URL{*lpurl},
		LCUrls: []url.URL{*lcurl},
		APUrls: []url.URL{*apurl},
		ACUrls: []url.URL{*acurl},

		ClusterState:        ClusterStateFlagNew,
		InitialClusterToken: "etcd-cluster",

		StrictReconfigCheck: DefaultStrictReconfigCheck,
		Metrics:             "basic",
		EnableV2:            DefaultEnableV2,

		CORS:          map[string]struct{}{"*": {}},
		HostWhitelist: map[string]struct{}{"*": {}},

		AuthToken:    "simple",
		BcryptCost:   uint(bcrypt.DefaultCost),
		AuthTokenTTL: 300,

		PreVote: true,

		loggerMu:              new(sync.RWMutex),
		logger:                nil,
		Logger:                "zap",
		LogOutputs:            []string{DefaultLogOutput},
		LogLevel:              logutil.DefaultLogLevel,
		EnableLogRotation:     false,
		LogRotationConfigJSON: DefaultLogRotationConfig,
		EnableGRPCGateway:     true,

		ExperimentalDowngradeCheckTime:           DefaultDowngradeCheckTime,
		ExperimentalMemoryMlock:                  false,
		ExperimentalTxnModeWriteWithSharedBuffer: true,
		ExperimentalMaxLearners:                  membership.DefaultMaxLearners,

		V2Deprecation: config.V2_DEPR_DEFAULT,
	}
	cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
	return cfg
}

func ConfigFromFile(path string) (*Config, error) {
	cfg := &configYAML{Config: *NewConfig()}
	if err := cfg.configFromFile(path); err != nil {
		return nil, err
	}
	return &cfg.Config, nil
}

func (cfg *configYAML) configFromFile(path string) error {
	b, err := os.ReadFile(path)
	if err != nil {
		return err
	}

	defaultInitialCluster := cfg.InitialCluster

	err = yaml.Unmarshal(b, cfg)
	if err != nil {
		return err
	}

	if cfg.LPUrlsJSON != "" {
		u, err := types.NewURLs(strings.Split(cfg.LPUrlsJSON, ","))
		if err != nil {
			fmt.Fprintf(os.Stderr, "unexpected error setting up listen-peer-urls: %v\n", err)
			os.Exit(1)
		}
		cfg.LPUrls = []url.URL(u)
	}

	if cfg.LCUrlsJSON != "" {
		u, err := types.NewURLs(strings.Split(cfg.LCUrlsJSON, ","))
		if err != nil {
			fmt.Fprintf(os.Stderr, "unexpected error setting up listen-client-urls: %v\n", err)
			os.Exit(1)
		}
		cfg.LCUrls = []url.URL(u)
	}

	if cfg.APUrlsJSON != "" {
		u, err := types.NewURLs(strings.Split(cfg.APUrlsJSON, ","))
		if err != nil {
			fmt.Fprintf(os.Stderr, "unexpected error setting up initial-advertise-peer-urls: %v\n", err)
			os.Exit(1)
		}
		cfg.APUrls = []url.URL(u)
	}

	if cfg.ACUrlsJSON != "" {
		u, err := types.NewURLs(strings.Split(cfg.ACUrlsJSON, ","))
		if err != nil {
			fmt.Fprintf(os.Stderr, "unexpected error setting up advertise-peer-urls: %v\n", err)
			os.Exit(1)
		}
		cfg.ACUrls = []url.URL(u)
	}

	if cfg.ListenMetricsUrlsJSON != "" {
		u, err := types.NewURLs(strings.Split(cfg.ListenMetricsUrlsJSON, ","))
		if err != nil {
			fmt.Fprintf(os.Stderr, "unexpected error setting up listen-metrics-urls: %v\n", err)
			os.Exit(1)
		}
		cfg.ListenMetricsUrls = []url.URL(u)
	}

	if cfg.CORSJSON != "" {
		uv := flags.NewUniqueURLsWithExceptions(cfg.CORSJSON, "*")
		cfg.CORS = uv.Values
	}

	if cfg.HostWhitelistJSON != "" {
		uv := flags.NewUniqueStringsValue(cfg.HostWhitelistJSON)
		cfg.HostWhitelist = uv.Values
	}

	// 如果设置了发现标志，请清除InitialClusterFromName设置的默认初始群集
	if (cfg.Durl != "" || cfg.DNSCluster != "") && cfg.InitialCluster == defaultInitialCluster {
		cfg.InitialCluster = ""
	}
	if cfg.ClusterState == "" {
		cfg.ClusterState = ClusterStateFlagNew
	}

	copySecurityDetails := func(tls *transport.TLSInfo, ysc *securityConfig) {
		tls.CertFile = ysc.CertFile
		tls.KeyFile = ysc.KeyFile
		tls.ClientCertFile = ysc.ClientCertFile
		tls.ClientKeyFile = ysc.ClientKeyFile
		tls.ClientCertAuth = ysc.CertAuth
		tls.TrustedCAFile = ysc.TrustedCAFile
	}
	copySecurityDetails(&cfg.ClientTLSInfo, &cfg.ClientSecurityJSON)
	copySecurityDetails(&cfg.PeerTLSInfo, &cfg.PeerSecurityJSON)
	cfg.ClientAutoTLS = cfg.ClientSecurityJSON.AutoTLS
	cfg.PeerAutoTLS = cfg.PeerSecurityJSON.AutoTLS
	if cfg.SelfSignedCertValidity == 0 {
		cfg.SelfSignedCertValidity = 1
	}
	return cfg.Validate()
}

func updateCipherSuites(tls *transport.TLSInfo, ss []string) error {
	if len(tls.CipherSuites) > 0 && len(ss) > 0 {
		return fmt.Errorf("TLSInfo.CipherSuites is already specified (given %v)", ss)
	}
	if len(ss) > 0 {
		cs := make([]uint16, len(ss))
		for i, s := range ss {
			var ok bool
			cs[i], ok = tlsutil.GetCipherSuite(s)
			if !ok {
				return fmt.Errorf("unexpected TLS cipher suite %q", s)
			}
		}
		tls.CipherSuites = cs
	}
	return nil
}

// 验证确保“*嵌入”。“配置”字段已正确配置。
func (cfg *Config) Validate() error {
	if err := cfg.setupLogging(); err != nil {
		return err
	}
	if err := checkBindURLs(cfg.LPUrls); err != nil {
		return err
	}
	if err := checkBindURLs(cfg.LCUrls); err != nil {
		return err
	}
	if err := checkBindURLs(cfg.ListenMetricsUrls); err != nil {
		return err
	}
	if err := checkHostURLs(cfg.APUrls); err != nil {
		addrs := cfg.getAPURLs()
		return fmt.Errorf(`--initial-advertise-peer-urls %q must be "host:port" (%v)`, strings.Join(addrs, ","), err)
	}
	if err := checkHostURLs(cfg.ACUrls); err != nil {
		addrs := cfg.getACURLs()
		return fmt.Errorf(`--advertise-client-urls %q must be "host:port" (%v)`, strings.Join(addrs, ","), err)
	}
	// 检查是否传递了冲突标志。
	nSet := 0
	for _, v := range []bool{cfg.Durl != "", cfg.InitialCluster != "", cfg.DNSCluster != ""} {
		if v {
			nSet++
		}
	}

	if cfg.ClusterState != ClusterStateFlagNew && cfg.ClusterState != ClusterStateFlagExisting {
		return fmt.Errorf("unexpected clusterState %q", cfg.ClusterState)
	}

	if nSet > 1 {
		return ErrConflictBootstrapFlags
	}

	if cfg.TickMs == 0 {
		return fmt.Errorf("--heartbeat-interval must be >0 (set to %dms)", cfg.TickMs)
	}
	if cfg.ElectionMs == 0 {
		return fmt.Errorf("--election-timeout must be >0 (set to %dms)", cfg.ElectionMs)
	}
	if 5*cfg.TickMs > cfg.ElectionMs {
		return fmt.Errorf("--election-timeout[%vms] should be at least as 5 times as --heartbeat-interval[%vms]", cfg.ElectionMs, cfg.TickMs)
	}
	if cfg.ElectionMs > maxElectionMs {
		return fmt.Errorf("--election-timeout[%vms] is too long, and should be set less than %vms", cfg.ElectionMs, maxElectionMs)
	}

	// 最后检查此项，因为在etcdmain中代理可能会使此项正常
	if cfg.LCUrls != nil && cfg.ACUrls == nil {
		return ErrUnsetAdvertiseClientURLsFlag
	}

	switch cfg.AutoCompactionMode {
	case "":
	case CompactorModeRevision, CompactorModePeriodic:
	default:
		return fmt.Errorf("unknown auto-compaction-mode %q", cfg.AutoCompactionMode)
	}

	// 验证分布式跟踪配置，但仅在启用时进行。
	if cfg.ExperimentalEnableDistributedTracing {
		if err := validateTracingConfig(cfg.ExperimentalDistributedTracingSamplingRatePerMillion); err != nil {
			return fmt.Errorf("distributed tracing configurition is not valid: (%v)", err)
		}
	}

	if !cfg.ExperimentalEnableLeaseCheckpointPersist && cfg.ExperimentalEnableLeaseCheckpoint {
		cfg.logger.Warn("Detected that checkpointing is enabled without persistence. Consider enabling experimental-enable-lease-checkpoint-persist")
	}

	if cfg.ExperimentalEnableLeaseCheckpointPersist && !cfg.ExperimentalEnableLeaseCheckpoint {
		return fmt.Errorf("setting experimental-enable-lease-checkpoint-persist requires experimental-enable-lease-checkpoint")
	}

	return nil
}

// PeerRurlsMapandToken为引导或发现设置初始对等URLsMap和集群令牌。
func (cfg *Config) PeerURLsMapAndToken(which string) (urlsmap types.URLsMap, token string, err error) {
	token = cfg.InitialClusterToken
	switch {
	case cfg.Durl != "":
		urlsmap = types.URLsMap{}
		// 如果使用发现，则基于
		// self的播发对等URL生成临时群集
		urlsmap[cfg.Name] = cfg.APUrls
		token = cfg.Durl

	case cfg.DNSCluster != "":
		clusterStrs, cerr := cfg.GetDNSClusterNames()
		lg := cfg.logger
		if cerr != nil {
			lg.Warn("failed to resolve during SRV discovery", zap.Error(cerr))
		}
		if len(clusterStrs) == 0 {
			return nil, "", cerr
		}
		for _, s := range clusterStrs {
			lg.Info("got bootstrap from DNS for etcd-server", zap.String("node", s))
		}
		clusterStr := strings.Join(clusterStrs, ",")
		if strings.Contains(clusterStr, "https:// /”&&cfg.PeerTLSInfo.TrustedCAFile==“”{
			cfg.PeerTLSInfo.ServerName = cfg.DNSCluster
		}
		urlsmap, err = types.NewURLsMap(clusterStr)
		// 只有etcd成员必须属于已发现的群集。
		// 代理不需要属于已发现的群集。
		if which == "etcd" {
			if _, ok := urlsmap[cfg.Name]; !ok {
				return nil, "", fmt.Errorf("cannot find local etcd member %q in SRV records", cfg.Name)
			}
		}

	default:
		// 我们进行了静态配置，并且已正确设置了群集。
		urlsmap, err = types.NewURLsMap(cfg.InitialCluster)
	}
	return urlsmap, token, err
}

// GetDNSClusterNames使用DNS SRV记录获取群集的初始节点列表ter bootstrapping.
// 此函数将返回一个或多个节点的列表，以及执行服务发现时遇到的任何错误。
// 注意：由于此函数将检查多组SRV记录，如果返回否，则发现应仅被视为失败de list为空。
func (cfg *Config) GetDNSClusterNames() ([]string, error) {
	var (
		clusterStrs       []string
		cerr              error
		serviceNameSuffix string
	)
	if cfg.DNSClusterServiceName != "" {
		serviceNameSuffix = "-" + cfg.DNSClusterServiceName
	}

	lg := cfg.GetLogger()

	// 同时使用etcd服务器ssl和etcd服务器进行查找。
	// 如果两者都可用，则合并结果。
	clusterStrs, cerr = getCluster("https", "etcd-server-ssl"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.APUrls)
	if cerr != nil {
		clusterStrs = make([]string, 0)
	}
	lg.Info(
		"get cluster for etcd-server-ssl SRV",
		zap.String("service-scheme", "https"),
		zap.String("service-name", "etcd-server-ssl"+serviceNameSuffix),
		zap.String("server-name", cfg.Name),
		zap.String("discovery-srv", cfg.DNSCluster),
		zap.Strings("advertise-peer-urls", cfg.getAPURLs()),
		zap.Strings("found-cluster", clusterStrs),
		zap.Error(cerr),
	)

	defaultHTTPClusterStrs, httpCerr := getCluster("http", "etcd-server"+serviceNameSuffix, cfg.Name, cfg.DNSCluster, cfg.APUrls)
	if httpCerr == nil {
		clusterStrs = append(clusterStrs, defaultHTTPClusterStrs...)
	}
	lg.Info(
		"get cluster for etcd-server SRV",
		zap.String("service-scheme", "http"),
		zap.String("service-name", "etcd-server"+serviceNameSuffix),
		zap.String("server-name", cfg.Name),
		zap.String("discovery-srv", cfg.DNSCluster),
		zap.Strings("advertise-peer-urls", cfg.getAPURLs()),
		zap.Strings("found-cluster", clusterStrs),
		zap.Error(httpCerr),
	)

	return clusterStrs, multierr.Combine(cerr, httpCerr)
}

func (cfg Config) InitialClusterFromName(name string) (ret string) {
	if len(cfg.APUrls) == 0 {
		return ""
	}
	n := name
	if name == "" {
		n = DefaultName
	}
	for i := range cfg.APUrls {
		ret = ret + "," + n + "=" + cfg.APUrls[i].String()
	}
	return ret[1:]
}

func (cfg Config) IsNewCluster() bool { return cfg.ClusterState == ClusterStateFlagNew }
func (cfg Config) ElectionTicks() int { return int(cfg.ElectionMs / cfg.TickMs) }

func (cfg Config) V2DeprecationEffective() config.V2DeprecationEnum {
	if cfg.V2Deprecation == "" {
		return config.V2_DEPR_DEFAULT
	}
	return cfg.V2Deprecation
}

func (cfg Config) defaultPeerHost() bool {
	return len(cfg.APUrls) == 1 && cfg.APUrls[0].String() == DefaultInitialAdvertisePeerURLs
}

func (cfg Config) defaultClientHost() bool {
	return len(cfg.ACUrls) == 1 && cfg.ACUrls[0].String() == DefaultAdvertiseClientURLs
}

func (cfg *Config) ClientSelfCert() (err error) {
	if !cfg.ClientAutoTLS {
		return nil
	}
	if !cfg.ClientTLSInfo.Empty() {
		cfg.logger.Warn("ignoring client auto TLS since certs given")
		return nil
	}
	chosts := make([]string, len(cfg.LCUrls))
	for i, u := range cfg.LCUrls {
		chosts[i] = u.Host
	}
	cfg.ClientTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "client"), chosts, cfg.SelfSignedCertValidity)
	if err != nil {
		return err
	}
	return updateCipherSuites(&cfg.ClientTLSInfo, cfg.CipherSuites)
}

func (cfg *Config) PeerSelfCert() (err error) {
	if !cfg.PeerAutoTLS {
		return nil
	}
	if !cfg.PeerTLSInfo.Empty() {
		cfg.logger.Warn("ignoring peer auto TLS since certs given")
		return nil
	}
	phosts := make([]string, len(cfg.LPUrls))
	for i, u := range cfg.LPUrls {
		phosts[i] = u.Host
	}
	cfg.PeerTLSInfo, err = transport.SelfCert(cfg.logger, filepath.Join(cfg.Dir, "fixtures", "peer"), phosts, cfg.SelfSignedCertValidity)
	if err != nil {
		return err
	}
	return updateCipherSuites(&cfg.PeerTLSInfo, cfg.CipherSuites)
}

// UpdatedFaultClusterFromName使用默认主机更新群集播发URL，如果播发URL是默认值，则
// 更新群集播发URL（本地主机：23792380）如果侦听URL为0.0.0.0.
// 例如，播发对等URL localhost:2380或侦听对等URL 0.0.0.0:2380 
// 则播发对等主机将更新为机器的默认主机
// 同时保留侦听URL的端口。
// 用户可以通过显式设置URL来解决此问题h 127.0.0.1.
// 返回默认主机名（如果使用）以及获取计算机默认主机时出现的错误（如果有）。
// TODO:检查是否设置了字段，而不是字段是否具有默认值
func (cfg *Config) UpdateDefaultClusterFromName(defaultInitialCluster string) (string, error) {
	if defaultHostname == "" || defaultHostStatus != nil {
		// 仅指定名称时更新“初始群集”（例如“etcd--name=abc”）
		if cfg.Name != DefaultName && cfg.InitialCluster == defaultInitialCluster {
			cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
		}
		return "", defaultHostStatus
	}

	used := false
	pip, pport := cfg.LPUrls[0].Hostname(), cfg.LPUrls[0].Port()
	if cfg.defaultPeerHost() && pip == "0.0.0.0" {
		cfg.APUrls[0] = url.URL{Scheme: cfg.APUrls[0].Scheme, Host: fmt.Sprintf("%s:%s", defaultHostname, pport)}
		used = true
	}
	// 仅指定名称时更新“initial cluster”（例如“etcd--name=abc”）
	if cfg.Name != DefaultName && cfg.InitialCluster == defaultInitialCluster {
		cfg.InitialCluster = cfg.InitialClusterFromName(cfg.Name)
	}

	cip, cport := cfg.LCUrls[0].Hostname(), cfg.LCUrls[0].Port()
	if cfg.defaultClientHost() && cip == "0.0.0.0" {
		cfg.ACUrls[0] = url.URL{Scheme: cfg.ACUrls[0].Scheme, Host: fmt.Sprintf("%s:%s", defaultHostname, cport)}
		used = true
	}
	dhost := defaultHostname
	if !used {
		dhost = ""
	}
	return dhost, defaultHostStatus
}

// 如果任何URL使用域名，CheckBindURL将返回错误。
func checkBindURLs(urls []url.URL) error {
	for _, url := range urls {
		if url.Scheme == "unix" || url.Scheme == "unixs" {
			continue
		}
		host, _, err := net.SplitHostPort(url.Host)
		if err != nil {
			return err
		}
		if host == "localhost" {
			// 本地地址的特殊情况
			// TODO:support/etc/hosts？
			continue
		}
		if net.ParseIP(host) == nil {
			return fmt.Errorf("expected IP in URL for binding (%s)", url.String())
		}
	}
	return nil
}

func checkHostURLs(urls []url.URL) error {
	for _, url := range urls {
		host, _, err := net.SplitHostPort(url.Host)
		if err != nil {
			return err
		}
		if host == "" {
			return fmt.Errorf("unexpected empty host (%s)", url.String())
		}
	}
	return nil
}

func (cfg *Config) getAPURLs() (ss []string) {
	ss = make([]string, len(cfg.APUrls))
	for i := range cfg.APUrls {
		ss[i] = cfg.APUrls[i].String()
	}
	return ss
}

func (cfg *Config) getLPURLs() (ss []string) {
	ss = make([]string, len(cfg.LPUrls))
	for i := range cfg.LPUrls {
		ss[i] = cfg.LPUrls[i].String()
	}
	return ss
}

func (cfg *Config) getACURLs() (ss []string) {
	ss = make([]string, len(cfg.ACUrls))
	for i := range cfg.ACUrls {
		ss[i] = cfg.ACUrls[i].String()
	}
	return ss
}

func (cfg *Config) getLCURLs() (ss []string) {
	ss = make([]string, len(cfg.LCUrls))
	for i := range cfg.LCUrls {
		ss[i] = cfg.LCUrls[i].String()
	}
	return ss
}

func (cfg *Config) getMetricsURLs() (ss []string) {
	ss = make([]string, len(cfg.ListenMetricsUrls))
	for i := range cfg.ListenMetricsUrls {
		ss[i] = cfg.ListenMetricsUrls[i].String()
	}
	return ss
}

func parseBackendFreelistType(freelistType string) bolt.FreelistType {
	if freelistType == freelistArrayType {
		return bolt.FreelistArrayType
	}

	return bolt.FreelistMapType
}
